using System.CodeDom;
using System.Collections;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using EnvDTE;
using CodeNamespace=System.CodeDom.CodeNamespace;

namespace NUnitGenAddIn.Core
{
    /// <summary>
    /// Unit Test Generator Helper
    /// </summary>
	public class UnitTestGeneratorHelper
	{
		private readonly CodeDomHelper _codeDomHelper;

		private const string NUNITFRAMEWORKDLLPATH = "nunit.framework";
		private const string UNITUNDERTESTMEMBERFIELDNAME = "_testClass";

        /// <summary>
        /// Gets the <see cref="NUnit"/> framework DLL path.
        /// </summary>
        /// <value>The <see cref="NUnit"/> framework DLL path.</value>
		public string NUnitFrameworkDllPath
		{
			get { return NUNITFRAMEWORKDLLPATH; }
		}

        /// <summary>
        /// Gets the name of the unit under test member field.
        /// </summary>
        /// <value>The name of the unit under test member field.</value>
		public string UnitUnderTestMemberFieldName
		{
			get { return UNITUNDERTESTMEMBERFIELDNAME; }
		}

        /// <summary>
        /// Initializes a new instance of the <see cref="UnitTestGeneratorHelper"/> class.
        /// </summary>
        /// <param name="codeDomHelper">The code DOM helper.</param>
		public UnitTestGeneratorHelper(CodeDomHelper codeDomHelper)
		{
			_codeDomHelper = codeDomHelper;
		}

        /// <summary>
        /// Gets the candidate member method list from class.
        /// </summary>
        /// <param name="targetClass">The target class.</param>
        /// <returns></returns>
		public ArrayList GetCandidateMemberMethodListFromClass(CodeClass targetClass)
		{
			ArrayList functionList = new ArrayList();
			foreach (CodeElement element in targetClass.Members)
			{
				if (element.Kind == vsCMElement.vsCMElementFunction)
				{
					CodeFunction targetFunction = element as CodeFunction;
					if (isFunctionEligibleForUnitTestGeneration(targetFunction))
						functionList.Add(targetFunction);
				}
			}
			return functionList;
		}

        /// <summary>
        /// Gets the candidate method list from function list.
        /// </summary>
        /// <param name="targetFunctionList">The target function list.</param>
        /// <returns></returns>
		public ArrayList GetCandidateMethodListFromFunctionList(ArrayList targetFunctionList)
		{
			ArrayList functionList = new ArrayList();
			foreach (CodeFunction function in targetFunctionList)
			{
				if (isFunctionEligibleForUnitTestGeneration(function))
					functionList.Add(function);
			}
			return functionList;
		}

        /// <summary>
        /// Creates the test fail method invoke expression.
        /// </summary>
        /// <param name="failMessage">The fail message.</param>
        /// <returns></returns>
		public CodeExpression CreateTestFailMethodInvokeExpression(string failMessage)
		{
			return
				new CodeMethodInvokeExpression(
					new CodeMethodReferenceExpression(new CodeTypeReferenceExpression("Assert"), "Fail"),
					new CodeExpression[] {new CodePrimitiveExpression(failMessage)});
		}

        /// <summary>
        /// Creates the are equal method invoke expression.
        /// </summary>
        /// <param name="expectedName">The expected name.</param>
        /// <param name="resultName">Name of the result.</param>
        /// <param name="testFailMessage">The test fail message.</param>
        /// <returns></returns>
		public CodeMethodInvokeExpression CreateAreEqualMethodInvokeExpression(string expectedName, string resultName,
		                                                                       string testFailMessage)
		{
			return
				new CodeMethodInvokeExpression(
					new CodeMethodReferenceExpression(new CodeTypeReferenceExpression("Assert"), "AreEqual"),
					new CodeExpression[]
						{
							new CodeVariableReferenceExpression(expectedName), new CodeVariableReferenceExpression(resultName),
							new CodePrimitiveExpression(testFailMessage)
						});
		}

        /// <summary>
        /// Creates the is not null method invoke expression.
        /// </summary>
        /// <param name="instanceName">Name of the instance.</param>
        /// <param name="testFailMessage">The test fail message.</param>
        /// <returns></returns>
		public CodeMethodInvokeExpression CreateIsNotNullMethodInvokeExpression(string instanceName, string testFailMessage)
		{
			return
				new CodeMethodInvokeExpression(
					new CodeMethodReferenceExpression(new CodeTypeReferenceExpression("Assert"), "IsNotNull"),
					new CodeExpression[]
						{new CodeVariableReferenceExpression(instanceName), new CodePrimitiveExpression(testFailMessage)});
		}

        /// <summary>
        /// Generates the initial unit test class code.
        /// </summary>
        /// <param name="targetNameSpace">The target <c>Namespace</c>.</param>
        /// <param name="targetClass">The target class.</param>
        /// <param name="testClassName">Name of the test class.</param>
        /// <returns></returns>
		public string GenerateInitialUnitTestClassCode(string targetNameSpace, CodeClass targetClass, string testClassName)
		{
			string targetClassName = targetClass.Name;
			CodeStatementCollection setupMethodStatementCollection = new CodeStatementCollection();
			CodeExpression[] constructorFuncParamsArray =
				prepareConstructorParameters(targetClass, setupMethodStatementCollection);

			CodeNamespace codeNamespace = new CodeNamespace(targetNameSpace);
			codeNamespace.Imports.Add(new CodeNamespaceImport("System"));
			codeNamespace.Imports.Add(new CodeNamespaceImport("NUnit.Framework"));
            
			CodeTypeDeclaration testClass = new CodeTypeDeclaration(testClassName);
            CodeCommentStatement statement = DocumentationHelper.GetDocumentationHeader(testClassName);
            testClass.Comments.Add(statement);
			testClass.CustomAttributes.Add(new CodeAttributeDeclaration("TestFixture"));
			codeNamespace.Types.Add(testClass);

			CodeMemberField testUnit = new CodeMemberField(targetClassName, UNITUNDERTESTMEMBERFIELDNAME);
			testClass.Members.Add(testUnit);

			CodeFieldReferenceExpression testUnitVariable = new CodeFieldReferenceExpression(null, testUnit.Name);

			CodeObjectCreateExpression currentClassInstance =
				new CodeObjectCreateExpression(targetClassName, constructorFuncParamsArray);
			setupMethodStatementCollection.Add(new CodeAssignStatement(testUnitVariable, currentClassInstance));
			testClass.Members.Add(createTestMemberMethod("SetUp", "SetUp", setupMethodStatementCollection));

			CodeStatement methodStatement = new CodeAssignStatement(testUnitVariable, new CodePrimitiveExpression(null));
			testClass.Members.Add(createTestMemberMethod("TearDown", "TearDown", methodStatement));
            
			StringBuilder code = new StringBuilder();
			StringWriter writer = new StringWriter(code);
			_codeDomHelper.GenerateCodeFromNameSpace(codeNamespace, writer, null);
			return code.ToString();
		}

        /// <summary>
        /// Creates the test member method.
        /// </summary>
        /// <param name="methodName">Name of the method.</param>
        /// <param name="attributeName">Name of the attribute.</param>
        /// <param name="methodStatement">The method statement.</param>
        /// <returns><see cref="CodeMemberMethod"/> object</returns>
		private static CodeMemberMethod createTestMemberMethod(string methodName, string attributeName, CodeStatement methodStatement)
		{
			CodeStatementCollection methodStatements = new CodeStatementCollection {methodStatement};

            return createTestMemberMethod(methodName, attributeName, methodStatements);
		}

        /// <summary>
        /// Creates the test member method.
        /// </summary>
        /// <param name="methodName">Name of the method.</param>
        /// <param name="attributeName">Name of the attribute.</param>
        /// <param name="methodStatements">The method statements.</param>
        /// <returns><see cref="CodeMemberMethod"/> object</returns>
		private static CodeMemberMethod createTestMemberMethod(string methodName, string attributeName,
		                                                CodeStatementCollection methodStatements)
		{
			CodeMemberMethod setupMethod = new CodeMemberMethod
			                                   {
			                                       Name = methodName,
			                                       Attributes = (MemberAttributes.Public | MemberAttributes.Final)
			                                   };

		    setupMethod.CustomAttributes.Add(new CodeAttributeDeclaration(attributeName));
            setupMethod.Comments.Add(DocumentationHelper.GetDocumentationHeader(methodName));
			setupMethod.Statements.AddRange(methodStatements);            
			return setupMethod;
		}

        /// <summary>
        /// Prepares the constructor parameters.
        /// </summary>
        /// <param name="targetClass">The target class.</param>
        /// <param name="setupMethodStatementCollection">The setup method statement collection.</param>
        /// <returns><see cref="CodeExpression"/> object</returns>
		private CodeExpression[] prepareConstructorParameters(CodeClass targetClass,
		                                                      CodeStatementCollection setupMethodStatementCollection)
		{
			CodeExpression[] constructorFuncParamsArray;
			CodeFunction targetClassConstructor = getConstructor(targetClass);
			if (targetClassConstructor != null)
			{
				CodeExpressionCollection constructorFuncParams = new CodeExpressionCollection();
				foreach (CodeParameter param in targetClassConstructor.Parameters)
				{
					setupMethodStatementCollection.Add(_codeDomHelper.CreateVariableDeclarationStatement(param.Type, param.Name));
					constructorFuncParams.Add(new CodeVariableReferenceExpression(param.Name));
				}
				constructorFuncParamsArray = new CodeExpression[constructorFuncParams.Count];
				constructorFuncParams.CopyTo(constructorFuncParamsArray, 0);
			}
			else
				constructorFuncParamsArray = new CodeExpression[] {};

			return constructorFuncParamsArray;
		}

        /// <summary>
        /// Gets the constructor.
        /// </summary>
        /// <param name="targetClass">The target class.</param>
        /// <returns></returns>
		private static CodeFunction getConstructor(CodeClass targetClass)
		{
			foreach (CodeElement element in targetClass.Members)
			{
				if (element.Kind == vsCMElement.vsCMElementFunction)
				{
					if (((CodeFunction) element).FunctionKind == vsCMFunction.vsCMFunctionConstructor)
						return element as CodeFunction;
				}
			}
			return null;
		}

        /// <summary>
        /// Determines whether [is function eligible for unit test generation] [the specified target function].
        /// </summary>
        /// <param name="targetFunction">The target function.</param>
        /// <returns>
        /// 	<c>true</c> if [is function eligible for unit test generation] [the specified target function]; otherwise, <c>false</c>.
        /// </returns>
		private static bool isFunctionEligibleForUnitTestGeneration(CodeFunction targetFunction)
		{
			bool eligible = false;
			vsCMAccess access = targetFunction.Access;
			if (access == vsCMAccess.vsCMAccessPublic
			    || access == vsCMAccess.vsCMAccessAssemblyOrFamily
			    || access == vsCMAccess.vsCMAccessProject
				//|| access == vsCMAccess.vsCMAccessPrivate
				//|| access == vsCMAccess.vsCMAccessProtected
				)
				eligible = true;

			foreach (CodeElement e in targetFunction.Attributes)
			{
				if (e.Name.Equals("Test") || e.Name.Equals("SetUp") || e.Name.Equals("TearDown"))
					eligible = false;
			}

			return eligible;
		}        
	}
}